#include "CoreApplication.h"
#include <SECore/EventDispatcher.h>
#include <SECore/Timer.h>
#include <chrono>

#ifdef _WIN32
#include <direct.h>
#elif __APPLE__ || __linux__
#include<unistd.h>
#endif

namespace SEngine
{
    CoreApplication * CoreApplication::s_app = nullptr;

    CoreApplication::CoreApplication()
    {
        s_app = this;
    }

    CoreApplication::~CoreApplication()
    {
        m_layers.RForeach([](std::string & name, std::unique_ptr<Layer> & layer) {
            layer->Detach();
        });
        m_layers.Clear();
    }

    void CoreApplication::Init()
    {
        // m_eventLoop.SendTask(std::bind(&CoreApplication::Update, std::ref(*this)));
        m_eventLoop.SendTask([](CoreApplication & app) { app.Update(); });
    }

    void CoreApplication::Exec()
    {
        s_lastTime = std::chrono::steady_clock::now();
        m_eventLoop.Start(60, *this);
    }

    CoreApplication &CoreApplication::App()
    { 
        return *s_app;
    }

    std::string CoreApplication::ApplicationCwd()
    {
        char buf[1024];
        _getcwd(buf, 1024);
        return buf;
    }

    void CoreApplication::SetWindow(std::unique_ptr<Window> && window)
    {
        m_window = std::move(window);
    }

    std::unique_ptr<Window> &CoreApplication::GetWindow()
    {
        return m_window;
    }

    void CoreApplication::PushFrontLayer(const std::string &name, std::unique_ptr<Layer> &&layer)
    {
        if (!m_layers.Has(name)) {
            layer->Attach();
            m_layers.EmplaceFront(name, std::move(layer));
        }
    }

    void CoreApplication::PushFrontLayer(std::unique_ptr<Layer> &&layer)
    {
        auto name = layer->Name();
        PushFrontLayer(name, std::move(layer));
    }

    void CoreApplication::PushBackLayer(const std::string & name, std::unique_ptr<Layer> &&layer)
    {
        if (!m_layers.Has(name)) {
            layer->Attach();
            m_layers.EmplaceBack(name, std::move(layer));
        }
    }

    void CoreApplication::PushBackLayer(std::unique_ptr<Layer> &&layer)
    {
        auto name = layer->Name();
        PushBackLayer(name, std::move(layer));
    }

    std::optional<std::reference_wrapper<Layer>> CoreApplication::GetLayer(const std::string &name)
    {
        if(m_layers.Has(name)) {
            return *m_layers.At(name);
        }
        return std::nullopt;
    }

    void CoreApplication::RemoveLayer(const std::string &name)
    {
        if (m_layers.Has(name)) {
            m_layers.At(name)->Detach();
            m_layers.Erase(name);
        }
    }

    void CoreApplication::Update()
    {
        m_eventLoop.SendTask([](CoreApplication & app) { app.Update(); });
        // m_eventLoop.SendTask(std::bind(&CoreApplication::Update, std::ref(*this)));
        auto currentTime = std::chrono::steady_clock::now();
        int delta = (int)std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - s_lastTime).count();
        s_lastTime = currentTime;
        if (m_window) {
            m_window->PollEvent(std::bind(&CoreApplication::OnEvent, this, std::placeholders::_1));
        }

        m_layers.Foreach([](std::string & name, std::unique_ptr<Layer> & layer) {
            if (layer->IsEnabled()) {
                layer->BeginFrame();
            }
        });
        m_layers.Foreach([delta](std::string & name, std::unique_ptr<Layer> & layer) {
            if (layer->IsEnabled()) {
                layer->Update(delta);
            }
        });
        m_layers.RForeach([delta](std::string & name, std::unique_ptr<Layer> & layer) {
            if (layer->IsEnabled()) {
                layer->EndFrame();
            }
        });

        Timer::Update(delta);
        if (m_window)
            m_window->SwapBuffer();
    }

    void CoreApplication::OnEvent(EventPtr event)
    {

        m_layers.Foreach([event](std::string & name, std::unique_ptr<Layer> & layer) {
            if (!(*event)) {
                return;
            }
            layer->OnEvent(event);
        });
        EventDispatcher dispatcher(event);
        dispatcher.Dispatcher<QuitEvent>(std::bind(&CoreApplication::OnQuitEvent, this, std::placeholders::_1));
    }

    void CoreApplication::OnQuitEvent(QuitEventPtr event)
    {
        m_eventLoop.Stop();
    }

} // namespace SEngine

